/*******************************************************************************
        
        copyright:		Copyright (C) Thomas Dixon 2008. All rights reserved
        				
        license:		BSD style: $(LICENSE)
        		
        author(s): 		Original version by Thomas Dixon
        				Modified by Mike Reinhold and Phil DeMonaco.
        				
*******************************************************************************/
module tango.util.cipher.RC6;

private import tango.util.cipher.Cipher;

/**
 * Implementation of the RC6-32/20/b cipher designed by 
 * Ron Rivest et al. of RSA Security.
 * 
 * It should be noted that this algorithm is very similar to RC5.
 * Currently there are no plans to implement RC5, but should that change
 * in the future, it may be wise to rewrite both RC5 and RC6 to use some
 * kind of template or base class.
 *
 * This algorithm is patented and trademarked.
 * 
 * References: http://people.csail.mit.edu/rivest/Rc6.pdf
 */
class RC6: BlockCipher {
	private {
		static const uint ROUNDS = 20, BLOCK_SIZE = 16, IV_SIZE = 16,
		// Magic constants for a 32 bit word size
				P = 0xb7e15163, Q = 0x9e3779b9;
		static const uint[] KEY_SIZES = [16,24,32];
		uint[] S;
		ubyte[] workingKey;
	}

	this(){
	}
	
	this(RC6 other){
		this.S = other.S.dup;
		this.workingKey = other.workingKey.dup;
		this._initialized = other._initialized;
		this._encrypt = other._encrypt;
	}
	
	RC6 dup() {
		return new RC6(this);
	}


	/***************************************************************************
	
			name
			
			Returns:
			the name of the cipher

	***************************************************************************/
	final override string name() {
		return "RC6";
	}


	/***************************************************************************
	
			blockSize
			
			Returns:
			the number of bytes in the cipher's block

	***************************************************************************/
	final override uint blockSize() {
		return BLOCK_SIZE;
	}
	
	/***************************************************************************
		
			ivSize
			
			Returns:
			size of the initialization vector used by this cipher.
	
	***************************************************************************/
	uint ivSize(){
		return IV_SIZE;
	}
	
	/***************************************************************************
	
			validKeySizes
						
			Returns:
			a dynamic array containing the key sizes which are valid 
			for this cipher.
	
	***************************************************************************/
	uint[] validKeySizes(){
		return KEY_SIZES;
	}


	/***************************************************************************
	
			init
			
			Params:
			encrypt = the initial direction of the cipher
			keyParams = the symmetric key used for the cipher
			
			Remarks:
			initializes the cipher 

	***************************************************************************/
	final void init(bool encrypt, SymmetricKey keyParams) {
		_encrypt = encrypt;

		uint len = keyParams.key.length;
		if(len != 16 && len != 24 && len != 32)
			invalid(name() ~ ": Invalid key length (requires 16/24/32 bytes)");

		S = new uint[2 * ROUNDS + 4];

		workingKey = keyParams.key;
		setup(workingKey);

		_initialized = true;
	}


	/***************************************************************************
	
			update
			
			Params:
			input_ = the input text to update
			output_ = the output buffer to store the updated text
			
			Returns:
			the number of bytes processed
			
			Remarks:
			this cipher must be initialized before use and the input and output buffers
			must be at least the length of a block.
			

	***************************************************************************/
	final override uint update(void[] input_, void[] output_) {
		if(!_initialized)
			invalid(name() ~ ": Cipher not initialized");

		ubyte[] input = cast(ubyte[]) input_, output = cast(ubyte[]) output_;

		if(input.length < BLOCK_SIZE)
			invalid(name() ~ ": Input buffer too short");

		if(output.length < BLOCK_SIZE)
			invalid(name() ~ ": Output buffer too short");

		uint A = ByteConverter.LittleEndian.to!(uint)(input[0 .. 4]),
				B = ByteConverter.LittleEndian.to!(uint)(input[4 .. 8]),
				C = ByteConverter.LittleEndian.to!(uint)(input[8 .. 12]),
				D = ByteConverter.LittleEndian.to!(uint)(input[12 .. 16]), t, u;

		if(_encrypt) {
			B += S[0];
			D += S[1];

			for(int i = 1; i <= ROUNDS; i++) {
				t = Bitwise.rotateLeft(B * ((B << 1) + 1), 5u);
				u = Bitwise.rotateLeft(D * ((D << 1) + 1), 5u);
				A = Bitwise.rotateLeft(A ^ t, u) + S[i << 1];
				C = Bitwise.rotateLeft(C ^ u, t) + S[(i << 1) + 1];
				t = A;
				A = B;
				B = C;
				C = D;
				D = t;
			}

			A += S[2 * ROUNDS + 2];
			C += S[2 * ROUNDS + 3];
		} else {
			C -= S[2 * ROUNDS + 3];
			A -= S[2 * ROUNDS + 2];

			for(int i = ROUNDS; i >= 1; i--) {
				t = D;
				D = C;
				C = B;
				B = A;
				A = t;
				u = Bitwise.rotateLeft(D * ((D << 1) + 1), 5u);
				t = Bitwise.rotateLeft(B * ((B << 1) + 1), 5u);
				C = Bitwise.rotateRight(C - S[(i << 1) + 1], t) ^ u;
				A = Bitwise.rotateRight(A - S[i << 1], u) ^ t;
			}

			D -= S[1];
			B -= S[0];
		}

		output[0 .. 4] = ByteConverter.LittleEndian.from!(uint)(A);
		output[4 .. 8] = ByteConverter.LittleEndian.from!(uint)(B);
		output[8 .. 12] = ByteConverter.LittleEndian.from!(uint)(C);
		output[12 .. 16] = ByteConverter.LittleEndian.from!(uint)(D);

		return BLOCK_SIZE;
	}


	/***************************************************************************
	
			reset
			
			Remarks:
			resets the cipher to its state just after the first call to init

	***************************************************************************/
	final override void reset() {
		setup(workingKey);
	}

	private void setup(ubyte[] key) {
		uint c = key.length / 4;
		uint[] L = new uint[c];
		for(int i = 0, j = 0; i < c; i++ , j += 4)
			L[i] = ByteConverter.LittleEndian.to!(uint)(
					key[j .. j + int.sizeof]);

		S[0] = P;
		for(int i = 1; i <= 2 * ROUNDS + 3; i++)
			S[i] = S[i - 1] + Q;

		uint A, B, i, j, v = 3 * (2 * ROUNDS + 4); // Relying on ints initializing to 0   
		for(int s = 1; s <= v; s++) {
			A = S[i] = Bitwise.rotateLeft(S[i] + A + B, 3u);
			B = L[j] = Bitwise.rotateLeft(L[j] + A + B, A + B);
			i = (i + 1) % (2 * ROUNDS + 4);
			j = (j + 1) % c;
		}
	}

	/** Some RC6 test vectors from the spec. */
	debug(UnitTest) {
		unittest {
			static string[]
					test_keys = [
							"00000000000000000000000000000000",
							"0123456789abcdef0112233445566778",
							"00000000000000000000000000000000" ~ "0000000000000000",
							"0123456789abcdef0112233445566778" ~ "899aabbccddeeff0",
							"00000000000000000000000000000000" ~ "00000000000000000000000000000000",
							"0123456789abcdef0112233445566778" ~ "899aabbccddeeff01032547698badcfe"];

			static string[] test_plaintexts = [
					"00000000000000000000000000000000",
					"02132435465768798a9bacbdcedfe0f1",
					"00000000000000000000000000000000",
					"02132435465768798a9bacbdcedfe0f1",
					"00000000000000000000000000000000",
					"02132435465768798a9bacbdcedfe0f1"];

			static string[] test_ciphertexts = [
					"8fc3a53656b1f778c129df4e9848a41e",
					"524e192f4715c6231f51f6367ea43f18",
					"6cd61bcb190b30384e8a3f168690ae82",
					"688329d019e505041e52e92af95291d4",
					"8f5fbd0510d15fa893fa3fda6e857ec2",
					"c8241816f0d7e48920ad16a1674e5d48"];

			RC6 t = new RC6();
			foreach(uint i, string test_key; test_keys) {
				ubyte[] buffer = new ubyte[t.blockSize];
				string result;
				SymmetricKey key = new SymmetricKey(ByteConverter.hexDecode(
						test_key));

				// Encryption
				t.init(true, key);
				t.update(ByteConverter.hexDecode(test_plaintexts[i]), buffer);
				result = ByteConverter.hexEncode(buffer);
				assert(result == test_ciphertexts[i], t.name ~ ": (" ~ result ~ ") != (" ~ test_ciphertexts[i] ~ ")");

				// Decryption
				t.init(false, key);
				t.update(ByteConverter.hexDecode(test_ciphertexts[i]), buffer);
				result = ByteConverter.hexEncode(buffer);
				assert(result == test_plaintexts[i], t.name ~ ": (" ~ result ~ ") != (" ~ test_plaintexts[i] ~ ")");
			}
		}
	}
}
